#include "StandardFuncsFX.fxh"

float4x4 g_matWorldViewProject;
float4 g_vMapSize_XZScale_YScale;
float g_fSeaLevelHeight;
float4 g_vQuadOffset_UseMultisample_Unused;

texture TerrainDiffuse;
sampler MeshTerrainDiffuse = sampler_state
{
	Texture = <TerrainDiffuse>;
	MagFilter = LINEAR;
	MipFilter = LINEAR;
	MinFilter = LINEAR;
	AddressU = Wrap;
	AddressV = Wrap;
};
texture HeightNormal;
sampler MeshHeightNormal = sampler_state
{
	Texture = <HeightNormal>;
	MagFilter = LINEAR;
	MipFilter = LINEAR;
	MinFilter = LINEAR;
	AddressU = Wrap;
	AddressV = Wrap;
};
texture TerrainColorTint;
sampler MeshTerrainColorTint = sampler_state
{
	Texture = <TerrainColorTint>;
	MagFilter = LINEAR;
	MipFilter = LINEAR;
	MinFilter = LINEAR;
	AddressU = Wrap;
	AddressV = Wrap;
};
texture TerrainNormal;
sampler MeshTerrainNormal = sampler_state
{
	Texture = <TerrainNormal>;
	MagFilter = LINEAR;
	MipFilter = POINT;
	MinFilter = LINEAR;
	AddressU = Wrap;
	AddressV = Wrap;
};
texture TerrainIDMap;
sampler MeshTerrainIDMap = sampler_state
{
	Texture = <TerrainIDMap>;
	MagFilter = POINT;
	MipFilter = NONE;
	MinFilter = POINT;
	AddressU = Mirror;
	AddressV = Mirror;
};

static const float TERRAINTILEFREQ = 128.0f;
static const float NUM_TILES = 4.0f;
static const float TEXELS_PER_TILE = 512.0f;
static const float ATLAS_TEXEL_POW2_EXPONENT= 11.0f;
static const float TERRAIN_WATER_CLIP_HEIGHT = 3.0f;

float mipmapLevel( float2 uv )
{
    float2 dx = ddx( uv * TEXELS_PER_TILE );
    float2 dy = ddy( uv * TEXELS_PER_TILE );
    float d = max( dot(dx, dx), dot(dy, dy) );
    return 0.5f * log2( d );
}

float4 sample_terrain( float fIndexU, float fIndexV, float2 vTileRepeat, float fMipTexels, float fLod )
{
	vTileRepeat = frac( vTileRepeat );

	float fTexelsPerTile = fMipTexels / NUM_TILES;

	vTileRepeat *= ( fTexelsPerTile - 1.0f ) / fTexelsPerTile;
	return float4(( float2( fIndexU, fIndexV ) + vTileRepeat ) / NUM_TILES + 0.5f / fMipTexels, 0.0f, fLod );
}

void calculate_index( float ID, out float IndexU, out float IndexV, out float vAllSame )
{
	ID *= 255.0f;
	vAllSame = saturate( ID - 98.0f ); // we've added 100 to first if all IDs are same
	ID -= vAllSame * 100.0f;

	IndexV = trunc( ( ID + 0.5f ) / NUM_TILES );
	IndexU = trunc( ID - ( IndexV * NUM_TILES ) + 0.5f );
}

struct VS_INPUT
{
	float2 Position			: POSITION;
	float Height			: TEXCOORD0;
};

struct VS_OUTPUT
{
	float4 Position			: POSITION;
	float3 PrePos			: TEXCOORD0;
	float2 TextureUV		: TEXCOORD1;
};

VS_OUTPUT RenderSceneVS( VS_INPUT Input )
{
	VS_OUTPUT Output = ( VS_OUTPUT )0;
	float2 vPrePos = Input.Position.xy + g_vQuadOffset_UseMultisample_Unused.xy;
	Output.TextureUV = float2( vPrePos.x / g_vMapSize_XZScale_YScale.x, ( g_vMapSize_XZScale_YScale.y - vPrePos.y - 1.f ) / g_vMapSize_XZScale_YScale.y );	// 纹理坐标转化
	float fHeight = Input.Height * g_vMapSize_XZScale_YScale.w;
	Output.PrePos = float3( vPrePos.x, fHeight, vPrePos.y );
	float2 vPos = vPrePos * g_vMapSize_XZScale_YScale.z;
	Output.Position = mul( float4( vPos.x, fHeight, vPos.y, 1.f ), g_matWorldViewProject );
	return Output;
}

float4 RenderScenePS( VS_OUTPUT Input ) : COLOR0
{
	clip( Input.PrePos.y + TERRAIN_WATER_CLIP_HEIGHT - g_fSeaLevelHeight );
	
	float fAllSame, fIndexU, fIndexV;
	calculate_index( tex2D( MeshTerrainIDMap, Input.TextureUV ).r, fIndexU, fIndexV, fAllSame );
	
	float2 vTileRepeat = Input.TextureUV * TERRAINTILEFREQ;
	vTileRepeat.x *= g_vMapSize_XZScale_YScale.x / g_vMapSize_XZScale_YScale.y;
	float fLod = clamp( trunc( mipmapLevel( vTileRepeat ) - 0.5f ), 0.f, 6.f );
	float fMipTexels = pow( 2.f, ATLAS_TEXEL_POW2_EXPONENT - fLod );
	
	float4 vSample = tex2Dlod( MeshTerrainDiffuse, sample_terrain( fIndexU, fIndexV, vTileRepeat, fMipTexels, fLod ));
	float3 vTerrainNormal = tex2Dlod( MeshTerrainNormal, sample_terrain( fIndexU, fIndexV, vTileRepeat, fMipTexels, fLod )).rbg - 0.5f;

	if ( fAllSame < 1.0f && g_vQuadOffset_UseMultisample_Unused.z <= 20.f )
	{
		float fAllSameTemp;
		float2 vUVOffset;
		float4 v4IndexU, v4IndexV;	// 上左下右

		// 无法使用v4IndexU[i] 所以不采用循环
		vUVOffset = float2( 0.f, -1.f / g_vMapSize_XZScale_YScale.y );
		calculate_index( tex2D( MeshTerrainIDMap, Input.TextureUV + vUVOffset ).r, v4IndexU[0], v4IndexV[0], fAllSameTemp );
		vUVOffset = float2( -1.f / g_vMapSize_XZScale_YScale.x, 0.f );
		calculate_index( tex2D( MeshTerrainIDMap, Input.TextureUV + vUVOffset ).r, v4IndexU[1], v4IndexV[1], fAllSameTemp );
		vUVOffset = float2( 0.f, 1.f / g_vMapSize_XZScale_YScale.y );
		calculate_index( tex2D( MeshTerrainIDMap, Input.TextureUV + vUVOffset ).r, v4IndexU[2], v4IndexV[2], fAllSameTemp );
		vUVOffset = float2( 1.f / g_vMapSize_XZScale_YScale.x, 0.f );
		calculate_index( tex2D( MeshTerrainIDMap, Input.TextureUV + vUVOffset ).r, v4IndexU[3], v4IndexV[3], fAllSameTemp );

		float4 vColor_Up = tex2Dlod( MeshTerrainDiffuse, sample_terrain( v4IndexU[0], v4IndexV[0], vTileRepeat, fMipTexels, fLod ));
		float4 vColor_Left = tex2Dlod( MeshTerrainDiffuse, sample_terrain( v4IndexU[1], v4IndexV[1], vTileRepeat, fMipTexels, fLod ));
		float4 vColor_Down = tex2Dlod( MeshTerrainDiffuse, sample_terrain( v4IndexU[2], v4IndexV[2], vTileRepeat, fMipTexels, fLod ));
		float4 vColor_Right = tex2Dlod( MeshTerrainDiffuse, sample_terrain( v4IndexU[3], v4IndexV[3], vTileRepeat, fMipTexels, fLod ));
		
		float3 vTerrainNormal_Up = tex2Dlod( MeshTerrainNormal, sample_terrain( v4IndexU[0], v4IndexV[0], vTileRepeat, fMipTexels, fLod )).rbg - 0.5f;
		float3 vTerrainNormal_Left = tex2Dlod( MeshTerrainNormal, sample_terrain( v4IndexU[1], v4IndexV[1], vTileRepeat, fMipTexels, fLod )).rbg - 0.5f;
		float3 vTerrainNormal_Down = tex2Dlod( MeshTerrainNormal, sample_terrain( v4IndexU[2], v4IndexV[2], vTileRepeat, fMipTexels, fLod )).rbg - 0.5f;
		float3 vTerrainNormal_Right = tex2Dlod( MeshTerrainNormal, sample_terrain( v4IndexU[3], v4IndexV[3], vTileRepeat, fMipTexels, fLod )).rbg - 0.5f;		

		float2 vFrac = frac( float2( Input.TextureUV.x * g_vMapSize_XZScale_YScale.x, Input.TextureUV.y * g_vMapSize_XZScale_YScale.y ));
		float vAlphaFactor = 10.0f;
		
		float4 v4Weight = float4(
			lerp( 1.f, 0.f, saturate( vFrac.y * 2.f )) * ( 1.f + vColor_Up.a * vAlphaFactor ),
			lerp( 1.f, 0.f, saturate( vFrac.x * 2.f )) * ( 1.f + vColor_Left.a * vAlphaFactor ),
			lerp( 1.f, 0.f, saturate(( 1.f - vFrac.y ) * 2.f )) * ( 1.f + vColor_Down.a * vAlphaFactor ),
			lerp( 1.f, 0.f, saturate(( 1.f - vFrac.x ) * 2.f )) * ( 1.f + vColor_Right.a * vAlphaFactor ));
		float fWeightSum = 1.f + vSample.a * vAlphaFactor + v4Weight.x + v4Weight.y + v4Weight.z + v4Weight.w;

		vSample = ( vSample * ( 1.f + vSample.a * vAlphaFactor ) +
					vColor_Up * v4Weight.x +		
					vColor_Left * v4Weight.y +
					vColor_Down * v4Weight.z +
					vColor_Right * v4Weight.w ) / fWeightSum;

		vTerrainNormal = ( vTerrainNormal * ( 1.f + vSample.a * vAlphaFactor ) +
						   vTerrainNormal_Up * v4Weight.x + 
						   vTerrainNormal_Left * v4Weight.y +
						   vTerrainNormal_Down * v4Weight.z +
						   vTerrainNormal_Right * v4Weight.w ) / fWeightSum;
	}

	vTerrainNormal = normalize( vTerrainNormal );

	float3 vNormal = normalize( tex2D( MeshHeightNormal, Input.TextureUV ).rbg - 0.5f );
	vNormal = vNormal.yxz * vTerrainNormal.x +
			  vNormal.xyz * vTerrainNormal.y +
			  vNormal.xzy * vTerrainNormal.z;

	float3 vTerrainColor = tex2D( MeshTerrainColorTint, Input.TextureUV ).rgb;
	vSample.rgb = GetOverlay( vSample.rgb, vTerrainColor, 0.75f );
	
	float3 vOut = CalculateLighting( vSample.rgb, vNormal );

	return float4( vOut, 1.0f );
}

technique TerrainColor
{
	pass P0
	{
		FillMode = Solid;
		VertexShader = compile vs_3_0 RenderSceneVS();
		PixelShader = compile ps_3_0 RenderScenePS();
	}
}
